读书笔记-深入分析 Java Web 技术内幕(一)

第一部分: Java Web 开发中的基础知识

1. 深入 Web 请求过程

1.1 HTTP 散点知识

Apache, IIS, Nginx, Tomcat, JBoss 都是基于 HTTP 的服务器, HTTP 是应用层的协议, 采用无状态的短连接通信方式

在浏览器里输入 www.baidu.com 敲击回撤的瞬间, 完成了以下操作

  • 浏览器请求 DNS 把域名解析成对应的 IP(域名到IP地址映射: DNS域名服务)

  • 浏览器根据这个 IP 在互联网上找到对应服务器, 发起 GET 请求, 返回数据资源

    • 请求 CDN 寻求静态资源

    • 请求的数据会先到负载均衡, 缓存, 如需要最终才到数据库

HttpClient 工具包 和 curl 命令都能够发起 HTTP 请求, 浏览器正是做了这些工作, 实际上就是建立一个 Socket 通信的过程

要理解 HTTP, 最重要的就是要熟悉 HTTP 中的 HTTP Header, HTTP Header 控制着用户浏览器的渲染行为和服务器的执行逻辑

显而易见, HTTP Request Header 是客户端要告诉服务器的信息, 而 HTTP Response Header 是服务器返回要告诉客户端的信息

Ctrl + F5 的实现逻辑是: 在请求头中加入了 Cache-Control: no-cache 和 Pragma: no-cache, 用于指定所有缓存机制在整个请求/响应链中必须服从的指令

其他用于标识缓存过期的头字段有: Expires, Last-Modified, Etag

1.2 DNS 散点知识

DNS 将域名转化为 IP 的先后关键步骤:

  • [本机完成] 1) 浏览器缓存 - 2) 操作系统hosts文件

  • [本机发起请求] 3) LDNS 即在操作系统中配置的 DNS 服务器地址 - 4) 直接请求 Root 域名服务器(全球就9台)

  • [本机得到反馈结果] 5) 返回给 LDNS 一个主域名服务器地址 gTLD

  • [本机发起请求] 6) LDNS 去请求 gTLD

  • [本机得到反馈结果] 7) 返回给 LDNS 网站注册的域名服务器 - 8)请求这个注册的域名服务器得到 IP - 9) LDNS 存储这个映射缓存 - 10) 存到本机(浏览器)

跟踪域名解析结果: dig www.baidu.com [+trace] [+cmd] 、 nslookup

清除域名缓存结果: ipconfig /flushdns 、/etc/init.d/nscd restart

几种域名解析的方式主要分为 A记录、MX记录、CNAME记录、NS记录 和 TXT记录

  • A记录: 域名对应 IP, 多个域名可以对应一个 IP, 但反之不能

  • MX记录: 邮件服务器对应的 IP

  • CNAME记录: 全称是 Canonical Name(别名解析), 一个域名可以设置多个别名

  • NS记录: 为某个域名指定 DNS 解析服务器, 也就是这个域名有指定的 IP 的 DNS 服务器去解析

  • TXT记录: 为某个主机名或域名设置说明类的文字

1.3 CDN 散点知识

形象的比喻: CDN = 镜像(Mirror) + 缓存(Cache) + 整体负载均衡(GSLB)

负载均衡的框架通常有三种: 链路负载均衡(DNS), 集群负载均衡(软硬件(LVS + HAProxy + F5)), 操作系统负载均衡(多队列网卡)

CDN 的动态加速技术原理就是在 CDN 的 DNS 解析中通过动态的链路探测来寻找回源最好的一条路径, 从而加速用户访问的效率, 节省带宽

2. 深入分析 Java I/O 的工作机制

2.1 Java I/O 类库的基础架构: java.io

  • 基于字节操作的 I/O 接口: InputStream 和 OutputStream

  • 基于字符操作的 I/O 接口: Writer 和 Reader

  • 基于磁盘操作的 I/O 接口: File

  • 基于网络操作的 I/O 接口: Socket

前两者是数据格式, 后两者是传输方式, 共同影响着 I/O 的效率

字符到字节必须经过编码转换, 这个过程相当耗时

在纯 Java 环境下, Java 序列化能够很好的工作, 但是在多语言环境下, 用 Java 序列化存储后, 很难用其他语言还原出结果。在这种情况下, 还是要尽量存储通用的数据机构, 如 JSON 或者 XML 结构数据, 当前也有比较好的序列化工具支持, 如 Google 的 protobuf 等

适配器和装饰器模式, 适配器是适配接口方便调用(InputStreamReader 适配了 InputStream 对象的 Reader 接口), 装饰器的增加接口功能提升效率(FileInputStream 装饰了 InputStream)

2.2 网络 I/O 的工作机制

影响网络传输的因素: 网络带宽, 传输距离, TCP 拥塞控制(为 TCP 设置一个缓冲区, 让传输方和接收方的步调一致)

Socket 一般都基于 TCP/IP 的流套接字

NIO 是相对于 BIO: 阻塞 I/O 来的, BIO 会导致线程阻塞等待操作系统处理, 在大规模访问量时性能堪忧, 当然可以采取线程池的方法来缓解, 但线程池中线程的创建与回收也需要成本, 大量长连接的请求会一直占用线程池, 线程优先级难以掌控, 众多因素呼唤出了 NIO

NIO 的两个关键词: Channel 和 Selector, Channel 好比传输方式, Selector 是这些传输方式的监控调度, Selector 是一个阻塞线程专门处理连接请求, 监控注册在其上面的 Channel 是否有数据传输发生, 一旦监控到了, 则让 Channel 进行相应的数据传输, 而 Channel 的数据传输发生在另一个非阻塞线程, 这样 Selector 不停的分发 I/O 链路让 Channel 进行通信, 换句话说:

Selector 检测到通信信道 I/O 有数据传输时, 通过 select() 方法取得 SocketChannel, 将数据读取或者写入 Buffer 缓冲区

NIO 提供了两个优化的文件访问方法: FileChannel.transferTo / FileChannel.transferFrom 和 FileChannel.map

2.3 I/O 调优

  • 磁盘 I/O 优化
  1. 增加缓存, 减少磁盘访问次数

  2. 优化磁盘的管理系统, 设计最优的磁盘方式策略, 以及磁盘的寻址策略(这是底层操作系统考虑的)

  3. 设计合理的磁盘存储数据块, 以及访问这些数据块的策略, 比如索引

  4. 应用合理的 RAID 策略提升磁盘 I/O

  • 网络 I/O 优化
  1. TCP 网络参数调优

1.1 cat /proc/net/netstat: 查看 TCP 的统计信息

1.2 cat /proc/net/snmp: 查看当前系统的连接情况

1.3 netstat -s: 查看网络的统计信息

1.4 ab -c 30 -n 100000 ip:port: 向 ip 并发发送 30 个请求共 100000 个

  1. 减少网络交互的次数: 两端设置缓存, 合并请求

  2. 减少网络传输数据量的大小: 压缩

  3. 尽量减少编码: 提前将字符转化成字节

  4. 交互方式的场景选择: 同步与异步/阻塞与非阻塞

3. 深入分析 Java Web 中的中文编码问题

3.1 几种常见的编码

在计算机中存储信息的最小单元是 1 个字节, 即 8 个 bit, 所以能表示的字符范围是 0~255 个, 人类要表示的符号太多, 无法用 1 个字节来完全表示, 要解决这个矛盾必须要有一个新的数据结构 char, 而从 char 到 byte 则必须编码

存储空间与编码效率: ASCII 码, ISO-8859-1, GB2312, GBK(兼容前者, 表示的汉字更多) UTF-16, UTF-8, 其中 UTF-8 是最适合中文的编码方式(不用查码表, 计算可寻, 单字节1汉字节3, 相对于UTF16扩张一倍空间而言, UTF8省), 编码的发生场景常见于存储数据到磁盘或者数据要经过网络传输

不同的编码决定着最终存储的大小, 看的是字节数而不是字符数, 一个 int 用 4 个字节存储, 一个 char 用 2 个字节存储

  • 一次 HTTP 请求在很多地方需要编码: URL 的编解码、HTTP Header 的编解码、POST 表单的编解码、HTTP BODY 的编解码
1
2
3
4
5
6
7
8
9
10
11
12
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
如需转载,请注明出处